﻿using System;
using System.Collections.Generic;
using System.Text;
using Scorpio;
using Scorpio.Exception;
namespace Scorpio.Compiler {
    /// <summary> 脚本语法解析 </summary>
    public partial class ScriptLexer {
        private LexState m_lexState;                //当前解析状态
        private LexState m_cacheLexState;           //缓存解析状态
        private bool m_SingleString;                //是否是单引号的字符串
        private String m_strToken = null;           //字符串token
        private int m_iCacheLine;                   //解析@字符串的时候记录其实行数
        private int m_iSourceLine;                  //当前解析行数
        private int m_iSourceChar;                  //当前解析字符
        private bool m_bFormatString;               //是否正在格式化字符串
        private String m_strBreviary;               //字符串的摘要 取第一行字符串的前20个字符
        private List<String> m_listSourceLines;     //所有行
        private List<Token> m_listTokens;           //解析后所得Token
        private char ch;                            //当前的解析的字符
        public ScriptLexer(String buffer, String strBreviary) {
            m_listSourceLines = new List<string>();
            m_listTokens = new List<Token>();
            string[] strLines = buffer.Split('\n');
            if (Util.IsNullOrEmpty(strBreviary)) {
                m_strBreviary = strLines.Length > 0 ? strLines[0] : "";
                if (m_strBreviary.Length > BREVIARY_CHAR)
                    m_strBreviary = m_strBreviary.Substring(0, BREVIARY_CHAR);
            } else {
                m_strBreviary = strBreviary;
            }
            foreach (String strLine in strLines)
                m_listSourceLines.Add(strLine + '\n');
            m_iSourceLine = 0;
            m_iSourceChar = 0;
            m_bFormatString = false;
            m_SingleString = false;
            lexState = LexState.None;
            m_cacheLexState = LexState.None;
        }
        /// <summary> 获得整段字符串的摘要 </summary>
        public String GetBreviary() {
            return m_strBreviary;
        }
        private bool IsIdentifier(char ch) {
            return (ch == '_' || char.IsLetterOrDigit(ch));
        }
        /// <summary> 解析字符串 </summary>
        public List<Token> GetTokens() {
            m_iSourceLine = 0;
            m_iSourceChar = 0;
            lexState = LexState.None;
            m_listTokens.Clear();
            while (!EndOfSource) {
                if (EndOfLine) {
                    IgnoreLine();
                    continue;
                }
                ch = ReadChar();
                switch (lexState) {
                case LexState.None:
                    switch (ch) {
                    case ' ':
                    case '\t':
                    case '\n':
                    case '\r':
                        break;
                    case '(':
                        AddToken(TokenType.LeftPar);
                        break;
                    case ')':
                        AddToken(TokenType.RightPar);
                        break;
                    case '[':
                        AddToken(TokenType.LeftBracket);
                        break;
                    case ']':
                        AddToken(TokenType.RightBracket);
                        break;
                    case '{':
                        AddToken(TokenType.LeftBrace);
                        break;
                    case '}':
                        if (m_bFormatString) {
                            m_bFormatString = false;
                            AddToken(TokenType.RightPar, ")");
                            AddToken(TokenType.Plus, "+");
                            lexState = LexState.SimpleString;
                            m_strToken = "";
                        } else {
                            AddToken(TokenType.RightBrace);
                        }
                        break;
                    case ',':
                        AddToken(TokenType.Comma);
                        break;
                    case ':':
                        AddToken(TokenType.Colon);
                        break;
                    case ';':
                        AddToken(TokenType.SemiColon);
                        break;
                    case '?':
                        AddToken(TokenType.QuestionMark);
                        break;
                    case '#':
                        AddToken(TokenType.Sharp);
                        break;
                    case '~':
                        AddToken(TokenType.Negative);
                        break;
                    case '.':
                        lexState = LexState.PeriodOrParams;
                        break;
                    case '+':
                        lexState = LexState.PlusOrIncrementOrAssignPlus;
                        break;
                    case '-':
                        lexState = LexState.MinusOrDecrementOrAssignMinus;
                        break;
                    case '*':
                        lexState = LexState.MultiplyOrAssignMultiply;
                        break;
                    case '/':
                        lexState = LexState.CommentOrDivideOrAssignDivide;
                        break;
                    case '%':
                        lexState = LexState.ModuloOrAssignModulo;
                        break;
                    case '=':
                        lexState = LexState.AssignOrEqual;
                        break;
                    case '&':
                        lexState = LexState.AndOrCombine;
                        break;
                    case '|':
                        lexState = LexState.OrOrInclusiveOr;
                        break;
                    case '!':
                        lexState = LexState.NotOrNotEqual;
                        break;
                    case '>':
                        lexState = LexState.GreaterOrGreaterEqual;
                        break;
                    case '<':
                        lexState = LexState.LessOrLessEqual;
                        break;
                    case '^':
                        lexState = LexState.XorOrAssignXor;
                        break;
                    case '@':
                        lexState = LexState.SimpleStringStart;
                        break;
                    case '\"':
                        m_SingleString = false;
                        lexState = LexState.String;
                        break;
                    case '\'':
                        m_SingleString = true;
                        lexState = LexState.String;
                        break;
                    default:
                        if (ch == '0') {
                            lexState = LexState.NumberOrHexNumber;
                            m_strToken = "";
                        } else if (char.IsDigit(ch)) {
                            lexState = LexState.Number;
                            m_strToken = "" + ch;
                        } else if (IsIdentifier(ch)) {
                            lexState = LexState.Identifier;
                            m_strToken = "" + ch;
                        } else {
                            ThrowInvalidCharacterException(ch);
                        }
                        break;
                    }
                    break;
                case LexState.PeriodOrParams:
                    if (ch == '.') {
                        lexState = LexState.Params;
                    } else {
                        AddToken(TokenType.Period, ".");
                        UndoChar();
                    }
                    break;
                case LexState.Params:
                    if (ch == '.') {
                        AddToken(TokenType.Params, "...");
                    } else {
                        ThrowInvalidCharacterException(ch);
                    }
                    break;
                case LexState.PlusOrIncrementOrAssignPlus:
                    if (ch == '+') {
                        AddToken(TokenType.Increment, "++");
                    } else if (ch == '=') {
                        AddToken(TokenType.AssignPlus, "+=");
                    } else {
                        AddToken(TokenType.Plus, "+");
                        UndoChar();
                    }
                    break;
                case LexState.MinusOrDecrementOrAssignMinus:
                    if (ch == '-') {
                        AddToken(TokenType.Decrement, "--");
                    } else if (ch == '=') {
                        AddToken(TokenType.AssignMinus, "-=");
                    } else {
                        AddToken(TokenType.Minus, "-");
                        UndoChar();
                    }
                    break;
                case LexState.MultiplyOrAssignMultiply:
                    if (ch == '=') {
                        AddToken(TokenType.AssignMultiply, "*=");
                    } else {
                        AddToken(TokenType.Multiply, "*");
                        UndoChar();
                    }
                    break;
                case LexState.CommentOrDivideOrAssignDivide:
                    if (ch == '/') {
                        lexState = LexState.LineComment;
                    } else if (ch == '*') {
                        lexState = LexState.BlockCommentStart;
                    } else if (ch == '=') {
                        AddToken(TokenType.AssignDivide, "/=");
                    } else {
                        AddToken(TokenType.Divide, "/");
                        UndoChar();
                    }
                    break;
                case LexState.ModuloOrAssignModulo:
                    if (ch == '=') {
                        AddToken(TokenType.AssignModulo, "%=");
                    } else {
                        AddToken(TokenType.Modulo, "%");
                        UndoChar();
                    }
                    break;
                case LexState.LineComment:
                    if (ch == '\n') {
                        lexState = LexState.None;
                    }
                    break;
                case LexState.BlockCommentStart:
                    if (ch == '*') {
                        lexState = LexState.BlockCommentEnd;
                    }
                    break;
                case LexState.BlockCommentEnd:
                    if (ch == '/') {
                        lexState = LexState.None;
                    } else {
                        lexState = LexState.BlockCommentStart;
                    }
                    break;
                case LexState.AssignOrEqual:
                    if (ch == '=') {
                        AddToken(TokenType.Equal, "==");
                    } else {
                        AddToken(TokenType.Assign, "=");
                        UndoChar();
                    }
                    break;
                case LexState.AndOrCombine:
                    if (ch == '&') {
                        AddToken(TokenType.And, "&&");
                    } else if (ch == '=') {
                        AddToken(TokenType.AssignCombine, "&=");
                    } else {
                        AddToken(TokenType.Combine, "&");
                        UndoChar();
                    }
                    break;
                case LexState.OrOrInclusiveOr:
                    if (ch == '|') {
                        AddToken(TokenType.Or, "||");
                    } else if (ch == '=') {
                        AddToken(TokenType.AssignInclusiveOr, "|=");
                    } else {
                        AddToken(TokenType.InclusiveOr, "|");
                        UndoChar();
                    }
                    break;
                case LexState.XorOrAssignXor:
                    if (ch == '=') {
                        AddToken(TokenType.AssignXOR, "^=");
                    } else {
                        AddToken(TokenType.XOR, "^");
                        UndoChar();
                    }
                    break;
                case LexState.GreaterOrGreaterEqual:
                    if (ch == '=') {
                        AddToken(TokenType.GreaterOrEqual, ">=");
                    } else if (ch == '>') {
                        lexState = LexState.ShrOrAssignShr;
                    } else {
                        AddToken(TokenType.Greater, ">");
                        UndoChar();
                    }
                    break;
                case LexState.LessOrLessEqual:
                    if (ch == '=') {
                        AddToken(TokenType.LessOrEqual, "<=");
                    } else if (ch == '<') {
                        lexState = LexState.ShiOrAssignShi;
                    } else {
                        AddToken(TokenType.Less, "<");
                        UndoChar();
                    }
                    break;
                case LexState.ShrOrAssignShr:
                    if (ch == '=') {
                        AddToken(TokenType.AssignShr, ">>=");
                    } else {
                        AddToken(TokenType.Shr, ">>");
                        UndoChar();
                    }
                    break;
                case LexState.ShiOrAssignShi:
                    if (ch == '=') {
                        AddToken(TokenType.AssignShi, "<<=");
                    } else {
                        AddToken(TokenType.Shi, "<<");
                        UndoChar();
                    }
                    break;
                case LexState.NotOrNotEqual:
                    if (ch == '=') {
                        AddToken(TokenType.NotEqual, "!=");
                    } else {
                        AddToken(TokenType.Not, "!");
                        UndoChar();
                    }
                    break;
                case LexState.String:
                    if (ch == (m_SingleString ? '\'' : '\"')) {
                        m_cacheLexState = LexState.None;
                        AddToken(TokenType.String, m_strToken);
                    } else if (ch == '\\') {
                        lexState = LexState.StringEscape;
                    } else if (ch == '\r' || ch == '\n') {
                        ThrowInvalidCharacterException(ch);
                    } else if (ch == '$') {
                        m_cacheLexState = LexState.String;
                        lexState = LexState.StringFormat;
                    } else {
                        m_strToken += ch;
                    }
                    break;
                case LexState.StringEscape:
                    if (ch == '\\' || ch == '\"') {
                        m_strToken += ch;
                    } else if (ch == 't') {
                        m_strToken += '\t';
                    } else if (ch == 'r') {
                        m_strToken += '\r';
                    } else if (ch == 'n') {
                        m_strToken += '\n';
                    } else {
                        m_strToken += ch;
                    }
                    lexState = LexState.String;
                    break;
                case LexState.SimpleStringStart:
                    if (ch != '\"' && ch != '\'') {
                        ThrowInvalidCharacterException(ch);
                    }
                    m_iCacheLine = m_iSourceLine;
                    lexState = LexState.SimpleString;
                    m_SingleString = (ch == '\'');
                    break;
                case LexState.SimpleString:
                    if (ch == (m_SingleString ? '\'' : '\"')) {
                        lexState = LexState.SimpleStringQuotationMarkOrOver;
                    } else if (ch == '$') {
                        m_cacheLexState = LexState.SimpleString;
                        lexState = LexState.StringFormat;
                    } else {
                        m_strToken += ch;
                    }
                    break;
                case LexState.SimpleStringQuotationMarkOrOver:
                    char c = m_SingleString ? '\'' : '\"';
                    if (ch == c) {
                        m_strToken += c;
                        lexState = LexState.SimpleString;
                    } else {
                        m_listTokens.Add(new Token(TokenType.SimpleString, m_strToken, m_iCacheLine, m_iSourceChar));
                        lexState = LexState.None;
                        UndoChar();
                    }
                    break;
                case LexState.StringFormat:
                    if (ch == '$') {
                        m_strToken += "$";
                        lexState = m_cacheLexState;
                    } else if (ch == '{') {
                        m_listTokens.Add(new Token(TokenType.SimpleString, m_strToken, m_iCacheLine, m_iSourceChar));
                        AddToken(TokenType.Plus, "+");
                        AddToken(TokenType.LeftPar, "(");
                        m_bFormatString = true;
                        m_strToken = "";
                    } else {
                        m_strToken += "$";
                        lexState = m_cacheLexState;
                        UndoChar();
                    }
                    break;
                case LexState.NumberOrHexNumber:
                    if (ch == 'x') {
                        lexState = LexState.HexNumber;
                    } else {
                        m_strToken = "0";
                        lexState = LexState.Number;
                        UndoChar();
                    }
                    break;
                case LexState.Number:
                    if (char.IsDigit(ch) || ch == '.') {
                        m_strToken += ch;
                    } else if (ch == 'L') {
                        long value = long.Parse(m_strToken);
                        AddToken(TokenType.Number, value);
                    } else {
                        double value = double.Parse(m_strToken);
                        AddToken(TokenType.Number, value);
                        UndoChar();
                    }
                    break;
                case LexState.HexNumber:
                    if (IsHexDigit(ch)) {
                        m_strToken += ch;
                    } else {
                        if (Util.IsNullOrEmpty(m_strToken))
                            ThrowInvalidCharacterException(ch);
                        long value = long.Parse(m_strToken, System.Globalization.NumberStyles.HexNumber);
                        AddToken(TokenType.Number, value);
                        UndoChar();
                    }
                    break;
                case LexState.Identifier:
                    if (IsIdentifier(ch)) {
                        m_strToken += ch;
                    } else {
                        TokenType tokenType;
                        switch (m_strToken) {
                        case "eval":
                            tokenType = TokenType.Eval;
                            break;
                        case "var":
                        case "local":
                            tokenType = TokenType.Var;
                            break;
                        case "function":
                            tokenType = TokenType.Function;
                            break;
                        case "if":
                            tokenType = TokenType.If;
                            break;
                        case "elseif":
                        case "elif":
                            tokenType = TokenType.ElseIf;
                            break;
                        case "else":
                            tokenType = TokenType.Else;
                            break;
                        case "while":
                            tokenType = TokenType.While;
                            break;
                        case "for":
                            tokenType = TokenType.For;
                            break;
                        case "foreach":
                            tokenType = TokenType.Foreach;
                            break;
                        case "in":
                            tokenType = TokenType.In;
                            break;
                        case "switch":
                            tokenType = TokenType.Switch;
                            break;
                        case "case":
                            tokenType = TokenType.Case;
                            break;
                        case "default":
                            tokenType = TokenType.Default;
                            break;
                        case "try":
                            tokenType = TokenType.Try;
                            break;
                        case "catch":
                            tokenType = TokenType.Catch;
                            break;
                        case "throw":
                            tokenType = TokenType.Throw;
                            break;
                        case "continue":
                            tokenType = TokenType.Continue;
                            break;
                        case "break":
                            tokenType = TokenType.Break;
                            break;
                        case "return":
                            tokenType = TokenType.Return;
                            break;
                        case "define":
                            tokenType = TokenType.Define;
                            break;
                        case "ifndef":
                            tokenType = TokenType.Ifndef;
                            break;
                        case "endif":
                            tokenType = TokenType.Endif;
                            break;
                        case "null":
                        case "nil":
                            tokenType = TokenType.Null;
                            break;
                        case "true":
                        case "false":
                            tokenType = TokenType.Boolean;
                            break;
                        default:
                            tokenType = TokenType.Identifier;
                            break;
                        }
                        if (tokenType == TokenType.Boolean) {
                            m_listTokens.Add(new Token(tokenType, m_strToken == "true", m_iSourceLine, m_iSourceChar));
                        } else if (tokenType == TokenType.Null) {
                            m_listTokens.Add(new Token(tokenType, null, m_iSourceLine, m_iSourceChar));
                        } else {
                            m_listTokens.Add(new Token(tokenType, m_strToken, m_iSourceLine, m_iSourceChar));
                        }
                        UndoChar();
                        lexState = LexState.None;
                    }
                    break;
                }
            }
            m_listTokens.Add(new Token(TokenType.Finished, "", m_iSourceLine, m_iSourceChar));
            return m_listTokens;
        }
    }
}
